/**
 * FPCalc - Floating Point Calculator
 *
 * @author Chris Pratt
 *
 * 12/26/2012
 */
package com.anodyzed.onyx.util;

public class FPCalc {
  private static final char NONE = (char)-1;
  private static final char SPC  = ' ';
  private static final char LEFT = '(';
  private static final char RITE = ')';
  private static final char ADD  = '+';
  private static final char SUB  = '-';
  private static final char MUL  = '*';
  private static final char DIV  = '/';
  private static final char MOD  = '%';
  private static final char DOT  = '.';

  /**
   * Constructor
   */
  private FPCalc () {
  } //FPCalc

  /**
   * Parse the Number from the Equation
   *
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The Numeric Value
   */
  private static double getNum (char[] equation,int[] pos) {
    boolean neg = false;
    double val = 0.0;
    while((pos[0] < equation.length) && (equation[pos[0]] == SPC)) {
      pos[0]++;
    }
    if((pos[0] < equation.length) && (equation[pos[0]] == LEFT)) {
      pos[0]++;
      return eval(equation,pos);
    } else {
      if((pos[0] < equation.length) && ((equation[pos[0]] == SUB) || (equation[pos[0]] == ADD))) {
        neg = equation[pos[0]++] == SUB;
      }
      while((pos[0] < equation.length) && Character.isDigit(equation[pos[0]])) {
        val = (val * 10) + Character.digit(equation[pos[0]++],10);
      }
      if((pos[0] < equation.length) && (equation[pos[0]] == DOT)) {
        pos[0]++;
        double fraction = 0.1;
        while((pos[0] < equation.length) && Character.isDigit(equation[pos[0]])) {
          val = val + (Character.digit(equation[pos[0]++],10) * fraction);
          fraction = fraction / 10;
        }
      }
      if(neg) {
        val *= -1;
      }
      return val;
    }
  } //getNum

  /**
   * Get a single Equation Term
   *
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The term value
   */
  private static double getTerm (char[] equation,int[] pos) {
    double val = getNum(equation,pos);
    while(pos[0] < equation.length) {
      switch(getOperator(equation,pos)) {
        case LEFT:
          pos[0]++;
          return eval(equation,pos);
        case MUL:
          pos[0]++;
          val *= getNum(equation,pos);
          break;
        case MOD:
          pos[0]++;
          val %= getNum(equation,pos);
        case DIV:
          pos[0]++;
          val /= getNum(equation,pos);
          break;
        default:
          return val;
      }
    }
    return val;
  } //getTerm

  /**
   * Locate the next Operator
   *
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The Operator Character
   */
  private static char getOperator (char[] equation,int[] pos) {
    while(pos[0] < equation.length) {
      switch(equation[pos[0]]) {
        case SPC:
            // Skip it
          pos[0]++;
          break;
        case '{':
        case '[':
          return LEFT;
        case '}':
        case ']':
          return RITE;
        case LEFT:
        case RITE:
        case ADD:
        case SUB:
        case MUL:
        case DIV:
          return equation[pos[0]];
        default:
          throw new IllegalArgumentException("Parse Error at position " + pos[0] + " (" + equation[pos[0]] + ')');
      }
    }
    return NONE;
  } //getOperator

  /**
   * Evaluate the specified equation from the specified position and with the
   * given starting value
   *
   * @param val The Initial value
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The result of the evaluation
   */
  private static double eval (double val,char[] equation,int[] pos) {
    char op;
    while(pos[0] < equation.length) {
      switch(op = getOperator(equation,pos)) {
        case NONE:
        case RITE:
          pos[0]++;
          return val;
        case ADD:
          pos[0]++;
          val += getTerm(equation,pos);
          break;
        case SUB:
          pos[0]++;
          val -= getTerm(equation,pos);
          break;
        default:
          throw new IllegalArgumentException("Unrecognized Operation (" + op + ") at position " + pos[0]);
      }
    }
    return val;
  } //eval

  /**
   * Evaluate the specified equation from the specified position
   *
   * @param equation The Equation Character Array
   * @param pos The Current Position Pointer in the Array
   * @return The result of the evaluation
   */
  private static double eval (char[] equation,int[] pos) {
    return eval(getTerm(equation,pos),equation,pos);
  } //eval

  /**
   * Evaluate the specified equation
   *
   * @param equation The Equation String
   * @return The result of the Evaluation
   */
  public static double eval (String equation) {
    return eval(equation.toCharArray(),new int[] {0});
  } //eval

  /**
   * Evaluate the specified equation
   *
   * @param val The Initial value of the equation
   * @param equation The Equation String
   * @return The result of the Evaluation
   */
  public static double eval (double val,String equation) {
    return eval(val,equation.toCharArray(),new int[] {0});
  } //eval

  /**
   * Evaluate the specified equation and return the answer as a string
   *
   * @param equation The Equation String
   * @return The Result of the Evaluation String
   */
  public static String toString (String equation) {
    return String.valueOf(eval(equation.toCharArray(),new int[] {0}));
  } //toString

  /**
   * Evaluate the specified equation and return the answer as a String
   *
   * @param val The Initial value of the equation
   * @param equation The Equation String
   * @return The Result of the Evaluation as a String
   */
  public static String toString (double val,String equation) {
    return String.valueOf(eval(val,equation.toCharArray(),new int[] {0}));
  } //toString

} //*FPCalc
